﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using Common.Win32;

namespace Common.IO
{
    public class AlternateFileStream : FileStream
    {
        private const Int32 DefaultBufferSize = 4096;

        public AlternateFileStream(String path, String streamName, FileMode mode)
            : this(path, streamName, mode, FileAccess.Read, FileShare.None, DefaultBufferSize, false)
        {
        }

        public AlternateFileStream(String path, String streamName, FileMode mode, FileAccess access)
            : this(path, streamName, mode, access, FileShare.None, DefaultBufferSize, false)
        {
        }

        public AlternateFileStream(String path, String streamName, FileMode mode, FileAccess access, FileShare share)
            : this(path, streamName, mode, access, share, DefaultBufferSize, false)
        {
        }

        public AlternateFileStream(String path, String streamName, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
            : this(path, streamName, mode, access, share, bufferSize, false)
        {
        }

        public AlternateFileStream(String path, String streamName, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
            : base(Native.OpenAlternateStream(path, streamName, mode, access, share, useAsync), access, bufferSize, useAsync)
        {
        }

        private static class Native
        {
            private const Int32 MaxPath = 256;
            private const String LongPathPrefix = @"\\?\";
            private const Char StreamSeparator = ':';

            public static SafeFileHandle OpenAlternateStream(String path, String streamName, FileMode mode, FileAccess access, FileShare share, Boolean useAsync)
            {
                NativeFileFlags flags = useAsync ? NativeFileFlags.Overlapped : 0;
                SafeFileHandle handle = CreateFile(BuildStreamPath(path, streamName), ToNative(access), share, IntPtr.Zero, mode, flags, IntPtr.Zero);

                if (handle.IsInvalid)
                {
                    MarshalEx.ThrowLastWin32Error();
                }

                return handle;
            }

            private static NativeFileAccess ToNative(FileAccess access)
            {
                NativeFileAccess result = 0;
                if ((FileAccess.Read & access) == FileAccess.Read)
                {
                    result |= NativeFileAccess.GenericRead;
                }

                if ((FileAccess.Write & access) == FileAccess.Write)
                {
                    result |= NativeFileAccess.GenericWrite;
                }

                return result;
            }

            private static String BuildStreamPath(String filePath, String streamName)
            {
                String result = Path.GetFullPath(filePath);

                if (!String.IsNullOrEmpty(filePath))
                {
                    if (1 == result.Length)
                    {
                        result = ".\\" + result;
                    }

                    result += StreamSeparator + streamName + StreamSeparator + "$DATA";

                    if (MaxPath <= result.Length)
                    {
                        result = LongPathPrefix + result;
                    }
                }

                return result;
            }

            [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
            private static extern SafeFileHandle CreateFile(
                String name,
                NativeFileAccess access,
                FileShare share,
                IntPtr security,
                FileMode mode,
                NativeFileFlags flags,
                IntPtr template);

            [Flags]
            private enum NativeFileFlags : uint
            {
                WriteThrough = 0x80000000,
                Overlapped = 0x40000000,
                NoBuffering = 0x20000000,
                RandomAccess = 0x10000000,
                SequentialScan = 0x8000000,
                DeleteOnClose = 0x4000000,
                BackupSemantics = 0x2000000,
                PosixSemantics = 0x1000000,
                OpenReparsePoint = 0x200000,
                OpenNoRecall = 0x100000
            }

            [Flags]
            private enum NativeFileAccess : uint
            {
                GenericRead = 0x80000000,
                GenericWrite = 0x40000000
            }
        }
    }
}
